Skip to content

Claude agent — Phase 11: customizations / plugins#318113

Merged
TylerLeonhardt merged 6 commits into
mainfrom
tyleonha/claude-phase11-customizations
May 27, 2026
Merged

Claude agent — Phase 11: customizations / plugins#318113
TylerLeonhardt merged 6 commits into
mainfrom
tyleonha/claude-phase11-customizations

Conversation

@TylerLeonhardt
Copy link
Copy Markdown
Member

Phase 11 of the Claude agent provider in the agent host.

Behaviour

  • Workbench-pushed customizations. setClientCustomizations / setCustomizationEnabled flow through IAgentPluginManager into Options.plugins on the Claude SDK Query.
  • Server-side (SDK-discovered) customizations. Commands / agents / MCP servers from the live Query are projected as a single "Discovered in Claude" Open Plugins-conformant on-disk bundle so the workbench's plugin expander surfaces them next to client-pushed entries.

Design notes

  • `Query.reloadPlugins()` is parameterless — the SDK's plugin URI set is captured into `Options.plugins` at startup and is otherwise immutable. Any client-pushed change therefore triggers a yield-restart through the same rematerializer path used for client-tool changes. `send()`'s pre-flight runs a single rebind when either `toolDiff` or `clientCustomizationsDiff` is dirty; the rematerializer reads `clientCustomizationsDiff.consume()` so the new URI set lands in `Options.plugins` of the rebuilt `Query`.
  • `SessionClientCustomizationsDiff` drives dirty from the model state observable (widened equality covers `nonce`, `displayName`, `description`, `statusMessage`, `agents`, `pluginDir`, status, enablement). Same-URI content refreshes (nonce bumps) now correctly flip dirty.
  • `setClientCustomizations` runs inside the per-session sequencer so a fire-and-forget call from `AgentSideEffects` cannot race a first `sendMessage`.
  • `ClaudeSdkCustomizationBundler` writes a hashed, content-addressed on-disk tree under `IAgentPluginManager.basePath`. Repeated calls with the same SDK snapshot are nonce-stable and skip the rewrite. The on-disk tree is intentionally a cross-session warm cache.

Tests

New `customizations/` test folder mirrors the source structure:

  • `SessionClientCustomizationsDiff` — URI list, nonce, metadata, enablement, dirty + consume semantics.
  • Projector — client + server tier merge with per-URI enablement overlay.
  • Bundler — write layout, nonce stability, name sanitisation, working-directory namespacing, delete-on-change.

`claudeAgent.test.ts`:

  • Sync-and-toggle dispatch through the sequencer.
  • Rebind on customizations dirty (proves `reloadPlugins()` would not have sufficed).
  • Mid-turn race coverage.
  • Swallowed-SDK-snapshot fallback in `getSessionCustomizations`.

Draft for review. No telemetry impact, no proposed-API changes.

Copilot AI review requested due to automatic review settings May 23, 2026 16:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements Phase 11 of the Claude agent provider in the agent host by wiring client-pushed customizations/plugins into session state and projecting SDK-discovered customizations as an on-disk Open Plugins bundle so the workbench can surface both tiers consistently.

Changes:

  • Add per-session client customization state (synced snapshot + enablement overlay + dirty/consume semantics) and project enabled plugin dirs into Options.plugins at SDK startup/restart.
  • Add SDK-side customizations snapshotting and bundle them into a synthetic “Discovered in Claude” Open Plugins layout under IAgentPluginManager.basePath.
  • Wire the new flows through ClaudeAgent/ClaudeAgentSession and add unit/integration tests plus supporting docs/plan updates.
Show a summary per file
File Description
src/vs/platform/agentHost/test/node/customizations/claudeSessionCustomizationsProjector.test.ts Unit tests for merging client-pushed customizations with the discovered bundle.
src/vs/platform/agentHost/test/node/customizations/claudeSessionClientCustomizationsModel.test.ts Unit tests for client customization diff/model behavior (dirty/consume/enablement).
src/vs/platform/agentHost/test/node/customizations/claudeSdkCustomizationBundler.test.ts Unit tests for bundling SDK-discovered customizations into an Open Plugins tree.
src/vs/platform/agentHost/test/node/claudeSdkPipeline.test.ts Tests for new pipeline reloadPlugins forwarding.
src/vs/platform/agentHost/test/node/claudeSdkOptions.test.ts Tests for projecting plugin directories into SDK Options.plugins.
src/vs/platform/agentHost/test/node/claudeAgent.test.ts Phase 11 coverage: sync dispatch, fan-out toggles, rebind behavior, race coverage, snapshot failure fallback.
src/vs/platform/agentHost/test/node/claudeAgent.integrationTest.ts Stubs IAgentPluginManager for proxy-backed integration coverage.
src/vs/platform/agentHost/node/claude/roadmap.md Roadmap update (adds Phase 16 entry and sequencing note).
src/vs/platform/agentHost/node/claude/phase11-plan.md New Phase 11 plan/design document captured alongside implementation.
src/vs/platform/agentHost/node/claude/customizations/claudeSessionCustomizationsProjector.ts Projects client-pushed customizations + discovered bundle into protocol SessionCustomization[].
src/vs/platform/agentHost/node/claude/customizations/claudeSessionClientCustomizationsModel.ts New per-session observable model + diff tracking for client-pushed customizations/plugins.
src/vs/platform/agentHost/node/claude/customizations/claudeSdkCustomizationBundler.ts New bundler that materializes SDK discovery into a synthetic on-disk Open Plugins bundle.
src/vs/platform/agentHost/node/claude/claudeSdkPipeline.ts Adds plugin reload + resolved-customizations snapshot APIs used by session customization projection.
src/vs/platform/agentHost/node/claude/claudeSdkOptions.ts Adds plugins?: URI[] input and maps it to SDK Options.plugins.
src/vs/platform/agentHost/node/claude/claudeAgentSession.ts Owns per-session client customization state, passes plugins at materialize/rebind, and exposes merged session customizations.
src/vs/platform/agentHost/node/claude/claudeAgent.ts Implements customization-related IAgent surface: syncing, toggling, aggregation event, and session projection.

Copilot's findings

  • Files reviewed: 16/16 changed files
  • Comments generated: 2

@TylerLeonhardt
Copy link
Copy Markdown
Member Author

@copilot fix the lint errors and comments

Copy link
Copy Markdown
Contributor

Copilot AI commented May 23, 2026

@copilot fix the lint errors and comments

Addressed in aa39ee9. I fixed the CI type/lint failures and review feedback by correcting the customization URI test shape, adding the missing SlashCommand argumentHint in tests, and updating setEnabled to avoid dirtying state for default-enabled entries (with regression coverage).

@TylerLeonhardt
Copy link
Copy Markdown
Member Author

@copilot fix the test failure

Copy link
Copy Markdown
Contributor

Copilot AI commented May 23, 2026

@copilot fix the test failure

Fixed in 4cbfa10. The failing test was due to URI key normalization mismatch (https://a/ vs https://a) in claudeSessionClientCustomizationsModel.test.ts; I aligned the key to the protocol URI string shape so the disable assertion passes.

TylerLeonhardt added a commit that referenced this pull request May 26, 2026
The original plan described setCustomizationEnabled as defer-and-coalesce
via Query.reloadPlugins() with a tool-set-divergence escalation to rebind.
Council review during PR #318113 verified Query.reloadPlugins() is
parameterless in @anthropic-ai/claude-agent-sdk and cannot change the
plugin URI set captured into Options.plugins at startup, so any client-
pushed customization change ships as a yield-restart through the same
rematerializer path that client-tool changes use.

Rewrites the Phase 11 sections of roadmap.md and phase11-plan.md so the
docs match what was merged. Historical "original plan called for X"
notes preserved for context. Phase 11 marked DONE on the roadmap.
@TylerLeonhardt TylerLeonhardt force-pushed the tyleonha/claude-phase11-customizations branch 2 times, most recently from 46205ad to fa1cb60 Compare May 26, 2026 23:57
TylerLeonhardt and others added 6 commits May 26, 2026 17:45
Workbench-pushed customizations (setClientCustomizations /
setCustomizationEnabled) flow through IAgentPluginManager into
Options.plugins for the Claude SDK Query. Server-side
(SDK-discovered) commands / agents / MCP servers are projected as a
single "Discovered in Claude" Open Plugins-conformant on-disk bundle.

Notable design notes:

  - The SDK's Query.reloadPlugins() is parameterless and cannot
    change the plugin URI set after startup, so any client-side
    customization change triggers a yield-restart through the same
    rematerializer path used for client-tool changes. send()'s
    pre-flight runs a single rebind when either toolDiff or
    clientCustomizationsDiff is dirty.

  - SessionClientCustomizationsDiff drives dirty from the model
    state observable (not just enabledPluginPaths), so nonce bumps
    and metadata refreshes at the same URI are detected.

  - setClientCustomizations runs inside the per-session sequencer so
    a fire-and-forget call from AgentSideEffects cannot race a first
    sendMessage.

  - ClaudeSdkCustomizationBundler writes a hashed, content-addressed
    on-disk tree under the plugin manager's basePath. Repeated
    calls with the same SDK snapshot are nonce-stable and skip the
    rewrite. The on-disk tree is intentionally a cross-session warm
    cache.

Tests:
  - New customizations/ test folder mirrors the source structure:
    SessionClientCustomizationsDiff (URI list, nonce, metadata,
    enablement, dirty semantics), projector (client+server merge),
    bundler (write layout, nonce stability, name sanitisation,
    namespacing, delete-on-change).
  - claudeAgent.test.ts: sync-and-toggle dispatch, sequencer
    serialisation, rebind on customizations dirty, mid-turn race
    coverage, swallowed-SDK-snapshot fallback in
    getSessionCustomizations.
Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>
Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>
URI.file('/p/a').fsPath is '\p\a' on Windows, so the literal POSIX
string comparisons fail there. Compute expected via URI.file().fsPath
so the same path round-trip drives both sides of the assertion.
The original plan described setCustomizationEnabled as defer-and-coalesce
via Query.reloadPlugins() with a tool-set-divergence escalation to rebind.
Council review during PR #318113 verified Query.reloadPlugins() is
parameterless in @anthropic-ai/claude-agent-sdk and cannot change the
plugin URI set captured into Options.plugins at startup, so any client-
pushed customization change ships as a yield-restart through the same
rematerializer path that client-tool changes use.

Rewrites the Phase 11 sections of roadmap.md and phase11-plan.md so the
docs match what was merged. Historical "original plan called for X"
notes preserved for context. Phase 11 marked DONE on the roadmap.
- Add IAgent.changeAgent for Claude: pre-materialize stash, post-materialize
  rebind via dirty bit (SDK has no working runtime control to swap agent in
  place — applyFlagSettings({ agent }) exists but doesn't actually swap).
- Thread Options.agent through buildOptions / materialize / rematerializer
  and persist selection in the per-session metadata overlay so resume
  picks it up.
- ClaudeSdkCustomizationBundler now publishes CustomizationAgentRef.uri
  as the on-disk `agents/<name>.md` path (was a synthetic
  `claude-sdk-agent:/` scheme). The workbench customization harness
  needs a real file URI to parse via promptsService.parseNew — without
  it the agents never reached the picker.
- Hide 'general-purpose' (SDK default) from the picker via shared
  CLAUDE_SDK_DEFAULT_AGENT_NAME constant.
- Tests: 3 changeAgent cases (provisional / mid-session rebind /
  clear-to-undefined), bundler agent-URI shape.
@TylerLeonhardt TylerLeonhardt force-pushed the tyleonha/claude-phase11-customizations branch from fa1cb60 to cfff52e Compare May 27, 2026 20:51
@TylerLeonhardt TylerLeonhardt marked this pull request as ready for review May 27, 2026 21:32
@TylerLeonhardt TylerLeonhardt enabled auto-merge (squash) May 27, 2026 21:32
@TylerLeonhardt TylerLeonhardt merged commit 36c5718 into main May 27, 2026
25 checks passed
@TylerLeonhardt TylerLeonhardt deleted the tyleonha/claude-phase11-customizations branch May 27, 2026 22:43
@vs-code-engineering vs-code-engineering Bot added this to the 1.123.0 milestone May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants